/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import { Dependency, DependencyEnum, DependencyType } from './core/dependency-interface.js';
import { ModulePkgNode } from './dependency-manager.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';

/**
 * Module的依赖信息集合
 *
 * @since 2022/5/7
 */
export class ModuleDependencyInfo {
  /**
   * har module 依赖的map集合
   * key - module name
   * value -module package.json path
   *
   * @private
   */
  private readonly _moduleDependencyMap: Map<string, Dependency>;
  private readonly _moduleDependencyCategory: Record<string, [string, Dependency][]>;

  private readonly _npmDependencies: Dependency[];
  private readonly _allDependencies: Dependency[];
  private readonly _npmDependenciesObj: Record<DependencyType, Dependency[]>;
  private readonly _hasHspDependency: boolean;
  private readonly _self: Dependency;
  private readonly _modulePkgNode: ModulePkgNode;
  private readonly _projectPkgNode: ModulePkgNode | undefined;
  /**
   * 储存当前模块的直接依赖，注意：工程级的依赖，也视为模块的直接依赖，示例：
   * 1.模块级的oh-package.json5配置：
   * dependencies: dayjs, har1
   * dynamicDependencies: hsp1
   * devDependencies: pako
   * 2.工程级的oh-package.json5配置：
   * dependencies: har2
   * dynamicDependencies: hsp2
   * devDependencies: @ohos/hypium
   *
   * 则，根据依赖优先规格：模块dependencies > 模块dynamicDependencies  > 工程dependencies > 工程dynamicDependencies > 模块devDependencies > 工程devDependencies，
   * 收集结果如下：
   * [dayjs, har1, hsp1, har2, hsp2, pako, @ohos/hypium]
   * @private
   */
  private _directPkgNodes?: ModulePkgNode[];
  private readonly _log = OhosLogger.getLogger('module-dependency-info');

  constructor(
    self: Dependency, modulePkgNode: ModulePkgNode, projectPkgNode: ModulePkgNode | undefined,
    moduleDependencyMap: Map<string, Dependency>, npmDependencies: Dependency[]
  ) {
    this._self = self;
    this._modulePkgNode = modulePkgNode;
    this._projectPkgNode = projectPkgNode;
    this._moduleDependencyMap = moduleDependencyMap;
    this._moduleDependencyCategory = {};
    moduleDependencyMap.forEach((dependencyItem, name) => {
      const dependencyType = dependencyItem.getDependencyType();
      if (this._moduleDependencyCategory[dependencyType]) {
        this._moduleDependencyCategory[dependencyType].push([name, dependencyItem]);
      } else {
        this._moduleDependencyCategory[dependencyType] = [[name, dependencyItem]];
      }
    });
    const npmDependenciesObj: Record<any, Dependency[]> = {};
    npmDependencies.forEach((dependencyItem) => {
      if (npmDependenciesObj[dependencyItem.getDependencyType()]) {
        npmDependenciesObj[dependencyItem.getDependencyType()].push(dependencyItem);
      } else {
        npmDependenciesObj[dependencyItem.getDependencyType()] = [dependencyItem];
      }
    });
    this._npmDependenciesObj = npmDependenciesObj;
    this._npmDependencies = [
      ...(npmDependenciesObj[DependencyType.DEPENDENCY_TYPE_HAR] || []),
      ...(npmDependenciesObj[DependencyType.DEPENDENCY_TYPE_HSP] || []),
    ];
    this._allDependencies = npmDependencies;
    const npmHspDependencies = npmDependenciesObj[DependencyType.DEPENDENCY_TYPE_HSP];
    this._hasHspDependency = npmHspDependencies?.length > 0;
  }

  /**
   * 同名依赖收集优先级：模块dependencies > 模块dynamicDependencies  > 工程dependencies > 工程dynamicDependencies > 模块devDependencies > 工程devDependencies
   * 根据此规格，移除掉同名的依赖模块
   * @param nodes
   * @private
   */
  private keepDependencyNameUnique(nodes: ModulePkgNode[]) {
    const uniqueNodes: ModulePkgNode[] = [];
    const uniqueIds: Set<string> = new Set();
    nodes.forEach(node => {
      const id = node.npm.getDependencyName();
      if (uniqueIds.has(id)) {
        this._log.debug(`Dependency key: ${id} already exists, current dependency listed in ${node.npm.getDependencyEnum()} will be ignored.`);
        return;
      }
      uniqueIds.add(id);
      uniqueNodes.push(node);
    });
    return uniqueNodes;
  }

  /**
   * 同名依赖收集优先级：模块dependencies > 模块dynamicDependencies  > 工程dependencies > 工程dynamicDependencies > 模块devDependencies > 工程devDependencies
   * 根据此规格，合并模块级与工程级依赖
   * @param modulePkgNodes
   * @param projectPkgNodes
   * @private
   */
  private getMergedPkgNodes(modulePkgNodes: ModulePkgNode[], projectPkgNodes?: ModulePkgNode[]) {
    if (!projectPkgNodes?.length) {
      return modulePkgNodes;
    }
    return [
      // 模块级的dependencies和dynamicDependencies
      ...modulePkgNodes.filter(node => node.npm.getDependencyEnum() !== DependencyEnum.DEV_DEPENDENCIES),
      // 工程级的dependencies和dynamicDependencies
      ...projectPkgNodes.filter(node => node.npm.getDependencyEnum() !== DependencyEnum.DEV_DEPENDENCIES),
      // 模块级的devDependencies
      ...modulePkgNodes.filter(node => node.npm.getDependencyEnum() === DependencyEnum.DEV_DEPENDENCIES),
      // 工程级的devDependencies
      ...projectPkgNodes.filter(node => node.npm.getDependencyEnum() === DependencyEnum.DEV_DEPENDENCIES)
    ];
  }

  /**
   * 对字节码HAR用到全局依赖进行依赖回填
   * @param node
   * @param globalPkgNodes
   * @private
   */
  private backFillPkgNodes(node: ModulePkgNode, globalPkgNodes: ModulePkgNode[]) {
    const dependency = node.npm;
    if (!dependency.isByteCodeHarDependency()) {
      return;
    }
    const pkgJson = dependency.getPackageJsonObj() as any;
    // 在字节码HAR编译时的全局依赖
    const globalDependencyKeys: String[] = pkgJson?.metadata?.globalDependencies || [];
    if (!globalDependencyKeys.length) {
      return;
    }
    // 全局依赖回填，包括当前集成模块
    const globalDependencies: ModulePkgNode[] = [];
    globalDependencyKeys.forEach(key => {
      const globalNode = globalPkgNodes.find(v => v.npm.getDependencyName() === key);
      if (!globalNode) {
        this._log.debug(`Global dependency[${key}] used in byteCodeHar[${dependency.getPackageName()}] does not configure in current module or project.`);
        return;
      }
      globalDependencies.push(globalNode);
    })
    node.children.push(...globalDependencies.map(child => ({...child, parent: node})));
  }

  /**
   * 合并模块级依赖和工程级依赖，构建完整的依赖树，只涉及遍历，不涉及io
   * @private
   */
  private buildDirectPkgNodes(): ModulePkgNode[] {
    const modulePkgNodes = this._modulePkgNode.children;
    // 工程级依赖，需要移除掉自身
    const projectPkgNodes = this._projectPkgNode?.children
      .filter(node => node.npm.getDependencyRootPath() !== this._self.getDependencyRootPath());

    const directPkgNodes = this.keepDependencyNameUnique(this.getMergedPkgNodes(modulePkgNodes, projectPkgNodes));
    // 浅拷贝，不影响第一层修改
    const dependencyTree = directPkgNodes.map(node => ({ ...node }));
    // 浅拷贝，不影响返回结果
    const queue = [...dependencyTree];

    const pkgRoot2PkgNode: Map<string, ModulePkgNode> = new Map();
    while (queue.length) {
      const node = queue.shift()!;
      const dependency = node.npm;
      const pkgRoot = dependency.getDependencyRootPath();
      // 缓存，复用
      if (pkgRoot2PkgNode.has(pkgRoot)) {
        node.children = pkgRoot2PkgNode.get(pkgRoot)!.children;
        continue;
      }
      node.children = this.keepDependencyNameUnique(node.children).map(child => ({ ...child, parent: node }));
      this.backFillPkgNodes(node, directPkgNodes);
      queue.push(...node.children);
      pkgRoot2PkgNode.set(pkgRoot, node);
    }

    return dependencyTree;
  }

  getDirectPkgNodes() {
    if (!this._directPkgNodes) {
      this._directPkgNodes = this.buildDirectPkgNodes();
    }
    return this._directPkgNodes;
  }

  getSelfAsDependency(): Dependency {
    return this._self;
  }

  getModulePkgNode() {
    return this._modulePkgNode;
  }

  getProjectPkgNode() {
    return this._projectPkgNode;
  }

  /**
   * 依赖的本地模块与其模块名的键值对
   * @returns {Map<string, Dependency>}
   */
  getModuleDependencyMap(): Map<string, Dependency> {
    return this._moduleDependencyMap;
  }

  /**
   * 依赖的本地模块与其模块名的键值对
   * @returns {[string, Dependency][]}
   */
  getModuleDependenciesByType(targetType: string): [string, Dependency][] {
    const targetDependencies = this._moduleDependencyCategory[targetType];
    return targetDependencies ?? [];
  }

  /**
   * 依赖的鸿蒙依赖，包括本地开发态的模块与发布态的远程或者本地依赖
   * @returns {Dependency[]}
   */
  getNpmDependencies(): Dependency[] {
    return this._npmDependencies;
  }

  /**
   * 全部依赖，包括本地开发态的模块与发布态的远程或者本地依赖，so，ohpm，npm等依赖
   * @returns {Dependency[]}
   */
  getAllDependencies(): Dependency[] {
    return this._allDependencies;
  }

  /**
   * 判断当前模块是否有hsp依赖
   */
  hasHspDependency(): boolean {
    return this._hasHspDependency;
  }

  getHspDependencies() {
    const hspDependency = this._npmDependenciesObj[DependencyType.DEPENDENCY_TYPE_HSP];
    return hspDependency ?? [];
  }

  getHarDependencies() {
    const harDependency = this._npmDependenciesObj[DependencyType.DEPENDENCY_TYPE_HAR];
    return harDependency ?? [];
  }
}
